/*	--- 装配Bean --- 


我们前面讨论了为什么要使用【Spring的IoC容器】，因为让【容器】来为我们【创建并装配Bean】能获得很大的好处，那么到底【如何使用IoC容器】？【装配好的Bean】又如何使用？

----------------------------

我们来看一个【具体的用户】注册登录的例子。

整个工程的结构如下：

spring-ioc-appcontext
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── itranswarp
        │           └── learnjava
        │               ├── Main.java
        │               └── service
        │                   ├── MailService.java
        │                   ├── User.java
        │                   └── UserService.java
        └── resources
            └── application.xml


首先，我们用【Maven】创建工程并【引入spring-context依赖】：
----------------------------------------------------------
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itranswarp.learnjava</groupId>
    <artifactId>spring-ioc-appcontext</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <java.version>11</java.version>

        <spring.version>5.2.3.RELEASE</spring.version>
    </properties>

    <dependencies>
        <dependency>        				 <--- 引入spring-context依赖
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>
</project>
-----------------------------------------------------------------


我们先编写一个[class MailService]，用于在[用户登录]和[注册成功后]发送[邮件通知]： */
public class MailService {
	private ZoneId zoneId = ZoneId.systemDefault();

	public void setZoneId(ZoneId zoneId) {
		this.zoneId = zoneId;
	}

	public String getTime() {
		return ZonedDateTime.now(this.zoneId).format(DateTimeFormatter.ISO_ZONED_DATE_TIME);
	}

	public void sendLoginMail(User user) {
		System.err.println(String.format("Hi, %s! You are logged in at %s", user.getName(), getTime()));
	}

	public void sendRegistrationMail(User user) {
		System.out.println(String.format("Welcome, %s!", user.getName()));
	}
}


/*
再编写一个[class UserService]，实现[用户注册]和[用户登录]： */
public class UserService { 
	private MailService mailService; // UserService 调用 MailService

	public void setMailService(MailService mailService) {
		this.mailService = mailService;
	}

	private List<User> users = new ArrayList<>(List.of( // users
			new User(1, "bob@example.com", "password", "Bob"), // bob
			new User(2, "alice@example.com", "password", "Alice"), // alice
			new User(3, "tom@example.com", "password", "Tom")
		));

	public User login(String email, String password) {
		for (User user : users) {
			if (user.getEmail().equalsIgnoreCase(email) && user.getPassword().equals(password)) {

				mailService.sendLoginMail(user);
				return user;
			}
		}

		throw new RuntimeException("login failed.");
	}

	public User getUser(long id) {
		return this.users.stream()
				.filter(user -> user.getId() == id)
				.findFirst()
				.orElseThrow();
	}

	public User register(String email, String password, String name) {

		users.forEach( (user) -> {
			if (user.getEmail().equalsIgnoreCase(email)) { // 如果注册邮箱已存在
				throw new RuntimeException("email exist.");
			}
		});

		// 邮箱可注册
		User user = new User(users.stream()
					.mapToLong(u -> u.getId().max())
					.getAsLong(), email, password, name
				); 
			// users.stream().mapToLong(u -> u.getId().max()).getAsLong() 用于获取Id最新
		users.add(user);
		mailService.sendRegistrationMail(user);

		return user;

	}
}


/*
注意到【class UserService】通过【setMailService()方法】注入了【一个MailService】。

然后，我们需要编写一个【特定的application.xml配置文件】，告诉【Spring的IoC容器】应该【如何创建并组装Bean】： 
---------------------------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userService" class="com.itranswarp.learnjava.service.UserService">
        <property name="mailService" ref="mailService" />
    </bean>

    <bean id="mailService" class="com.itranswarp.learnjava.service.MailService" />
</beans>

--------------------------------------------------------

注意观察上述配置文件，其中与XML Schema相关的部分格式是固定的，我们只关注两个<bean ...>的配置：

	· 每个<bean ...>都有[一个id标识]，相当于[Bean的唯一ID]；

	· 在[userServiceBean]中，通过<property name="..." ref="..." /> 注入了【另一个Bean】；

	· 【Bean的顺序】不重要，Spring根据【依赖关系】会自动【正确初始化】。


把【上述XML配置文件】用【Java代码】写出来，就像这样： */
UserService uerService = new UserService();
MailService mailService = new MailService();
userService.setMailService(mailService);


/*
只不过【Spring容器】是通过【读取XML文件】后, 使用【反射】完成的。

-----------------------------------


如果注入的【不是Bean】，【而是boolean、int、String这样的数据类型】, 则通过【value注入】

例如，【创建一个HikariDataSource】：
(写入: /sprint-ioc-appcontext/src/resources/application.xml)
------------------------------------------------
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test" />
    <property name="username" value="root" />
    <property name="password" value="password" />
    <property name="maximumPoolSize" value="10" />
    <property name="autoCommit" value="true" />
</bean>
--------------------------------------------------------


最后一步，我们需要创建一个【Spring的IoC容器实例】，然后【加载配置文件】，

让【Spring容器】为我们【创建+装配 配置文件中指定的所有Bean】，这只需要【一行代码】： */
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");


/*
接下来，我们就可以从【Spring容器】中“取出”【装配好的Bean】, 然后使用Bean： */
// 获取Bean
UserService userService = context.getBean(UserService.class);
// 正常调用
User user = userService.login("bob@example.com", "password");


/*
【完整的main()方法】如下： */
public class Main {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
		UserService userService = context.getBean(UserService.class);
		User user = userService.login("bob@example.com", "password");
		System.out.println(user.getName());

	}
}

/*===========================================================

#	ApplicationContext

我们从【创建Spring容器】的代码： */
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

/*
可以看到，【Spring容器】就是【一个ApplicationContext】

【ApplicationContext】是【一个接口】，有很多【实现类】。

这里我们选择【ClassPathXmlApplicationContext】，表示它会【自动从classpath中查找指定的XML配置文件】。

-------------------------------------

获得了【ApplicationContext实例】，就获得了【IoC容器の引用】。

从【ApplicationContext】中我们可以根据【Bean的ID】获取Bean

但更多的时候我们根据【Bean的类型】获取【Bean的引用】： */
UserService userService = context.getBean(UserService.class);

/*
【Spring】还提供【另一种IoC容器】叫【BeanFactory】，使用方式和【ApplicationContext】类似： */
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("application.xml"));
MailService mailService = factory.getBean(MailService.class);


/*
IoC容器的2种模式
【BeanFactory】和【ApplicationContext】的区别在于:
	· 【BeanFactory】的实现是"按需创建"，即【第一次获取Bean时】才创建【这个Bean】
	· 【ApplicationContext】则是会"一次性"【创建所有的Bean】。

----------------------------------------


实际上，【ApplicationContext接口】是从【BeanFactory接口】继承而来的。

并且，【ApplicationContext】提供了【一些额外的功能】，包括【国际化支持、事件和通知机制等】。

PS: 通常情况下，我们总是【使用ApplicationContext】，【很少】会考虑【使用BeanFactory】。


===================================================


#	练习

在上述示例的基础上，继续给【UserService】注入【DataSource】，并把[注册]和[登录功能]通过[数据库]实现。 */





/*===============================================================

#	----- 装配Bean -----


1. 【Spring的IoC容器接口】是【ApplicationContext/BeanFactory】，并提供了【多种实现类】；
(多种实现类 = ClassPathXmlApplicationContext)

2. 通过【XML配置文件】创建【IoC容器】时，使用【ClassPathXmlApplicationContext】；

3. 持有【IoC容器】后，通过【getBean()方法】获取【Bean的引用】。

============================

配置【Maven的spring-context依赖】
Path = /spring-ioc-appcontext/pom.xml
----------------------------------------------
project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itranswarp.learnjava</groupId>
    <artifactId>spring-ioc-appcontext</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging> 		<---

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <java.version>11</java.version>

        <spring.version>5.2.3.RELEASE</spring.version>  <---
    </properties>

    <dependencies>
        <dependency>   
            <groupId>org.springframework</groupId>  <---
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version> <---
        </dependency>
    </dependencies>
</project>
----------------------------


=============================

Xml配置
Path = /sprint-ioc-appcontext/src/resources/application.xml
-----------------------------------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userService" class="com.itranswarp.learnjava.service.UserService">
        <property name="mailService" ref="mailService" />  <--ref注入class
    </bean>

    <bean id="mailService" class="com.itranswarp.learnjava.service.MailService" />

    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test" />
        <property name="username" value="root" />    <---value注入"赋值"
        <property name="password" value="password" />
        <property name="maximumPoolSize" value="10" />
        <property name="autoCommit" value="true" />
    </bean>
</beans>
------------------------------------------------

汇总：
	<bean id=".." class="..">
	</bean>
bean中,class是用于IoC的实例化
	ApplicationContext.getBean(UserService.class);

代码模板: */
public class Main {
	public static void main(String[] args) {

		// 使用IoC容器创建实例
		ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
		UserService userService = context.getBean(UserService.class);

		User user = userService.login("bob@example.com", "password");

		System.out.println(user.getName());

	}
}













































